package com.fwtai.pay.wechat.auth;

import com.fwtai.pay.wechat.Validator;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;

import static com.fwtai.pay.wechat.constant.WechatPayHttpHeaders.REQUEST_ID;
import static com.fwtai.pay.wechat.constant.WechatPayHttpHeaders.WECHAT_PAY_NONCE;
import static com.fwtai.pay.wechat.constant.WechatPayHttpHeaders.WECHAT_PAY_SERIAL;
import static com.fwtai.pay.wechat.constant.WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE;
import static com.fwtai.pay.wechat.constant.WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP;

public class WechatPay2Validator implements Validator {

  protected static final Logger log = LoggerFactory.getLogger(WechatPay2Validator.class);

  /**
   * 应答超时时间，单位为分钟
  */
  protected static final long RESPONSE_EXPIRED_MINUTES = 5;
  protected final Verifier verifier;

  public WechatPay2Validator(final Verifier verifier) {
    this.verifier = verifier;
  }

  protected static IllegalArgumentException parameterError(String message,final Object... args) {
    message = String.format(message, args);
    return new IllegalArgumentException("parameter error: " + message);
  }

  protected static IllegalArgumentException verifyFail(String message,final Object... args) {
    message = String.format(message, args);
    return new IllegalArgumentException("signature verify fail: " + message);
  }

  @Override
  public final boolean validate(final CloseableHttpResponse response) throws IOException {
    try {
      validateParameters(response);
      final String message = buildMessage(response);
      final String serial = response.getFirstHeader(WECHAT_PAY_SERIAL).getValue();
      final String signature = response.getFirstHeader(WECHAT_PAY_SIGNATURE).getValue();
      if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
        throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
          serial, message, signature, response.getFirstHeader(REQUEST_ID).getValue());
      }
    } catch (final IllegalArgumentException e) {
      log.warn(e.getMessage());
      return false;
    }
    return true;
  }

  protected final void validateParameters(final CloseableHttpResponse response) {
    final Header firstHeader = response.getFirstHeader(REQUEST_ID);
    if (firstHeader == null) {
      throw parameterError("empty " + REQUEST_ID);
    }
    final String requestId = firstHeader.getValue();
    // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
    final String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
    Header header = null;
    for (final String headerName : headers) {
      header = response.getFirstHeader(headerName);
      if (header == null) {
        throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
      }
    }
    final String timestampStr = header.getValue();
    try {
      Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
      // 拒绝过期应答
      if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
        throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
      }
    } catch (DateTimeException | NumberFormatException e) {
      throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
    }
  }

  protected final String buildMessage(final CloseableHttpResponse response) throws IOException {
    final String timestamp = response.getFirstHeader(WECHAT_PAY_TIMESTAMP).getValue();
    final String nonce = response.getFirstHeader(WECHAT_PAY_NONCE).getValue();
    final String body = getResponseBody(response);
    return timestamp + "\n"
      + nonce + "\n"
      + body + "\n";
  }

  protected final String getResponseBody(final CloseableHttpResponse response) throws IOException {
    final HttpEntity entity = response.getEntity();
    return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
  }
}